;What is this?
;->A "timer mode" for emacs
; 
;How to install?
;->In your .emacs file (or wherever), put
;(load "PATH-TO-timer-mode.el")
; 
;How to run?
;->Once the include is done, you can type M-x timer-mode, and it should be done
;  It also automatically runs when you open a file with a ".timer" extension in
;  emacs.
; 
;How do I use it?
;->Create a text file called "timesheet.timer"
;->Open it with emacs - you should go into timer-mode.
;->To start a new timer record, hit C-n - it will prompt you for a reference, description
;  and estimated time. When finished, it'll write the line to the buffer.
;->To start timing, hit C-t. To stop, you'll have to break the loop - use C-g.
;->To sum some times, go to the 1.234 formatted column - use C-space to set the mark in the top-left
;  corner of the figures you want to add, then move down to the bottom-right corner of the numbers
;  you want to add, and hit C-s. It'll ask you for additional hours, which you can ignore, and just press
;  enter - it'll have added the hours up for you.
;->To commit fraud, and pad your times, use C-space in the top-left corner and move to the bottom-
;  right corner of your hours, and hit C-f. It'll prompt you for the target hours, which you enter,
;  and all the tasks highlighted will be padded, proportional to the percentage of time they currently
;  occupy. The hh:mm:ss text will not be changed - you must either cover your tracks, or leave
;  it as-is, to show which records you adjusted :)
; 
;And that's about it. All values are separated by tabs, to look pretty, and, more importantly to dump
;into spreadsheets, so you can justify your existence.
; 
;I hope you enjoy.
;Ryan
;inspector.smith@gmail.com

;;some basic setup
(defconst timer-version "0.2")
(defvar timer-mode nil)
(defvar timer-mode-map nil)

  
(defvar timer-toggle-hook 'timer-mode-toggle)
(add-to-list 'auto-mode-alist '("\\.timer\\'" . timer-mode))

;YES! I've contravened the sacred C-c C-(whatever) rule, but since this thing takes over the whole
;app, I thought it'd be a waste of keystrokes to go that route.

(unless timer-mode-map
  (setq timer-mode-map (make-sparse-keymap))
  (define-key timer-mode-map "\C-t" 'timer-mode-start-timing)
  (define-key timer-mode-map "\C-n" 'timer-mode-new-record-at-bottom)
  (define-key timer-mode-map "\C-s" 'timer-mode-sum-hours)
  (define-key timer-mode-map "\C-f" 'timer-mode-make-nice)
  (define-key timer-mode-map "\C-d" 'timer-mode-new-day)
  (define-key timer-mode-map [delete] 'delete-char))
	

(defun timer-mode-toggle ()
  (if timer-mode ;timer-mode gets toggled when activating and de-activating the mode. SUPER
      (progn 
	(defun timer-mode-new-day ()
	  (interactive)
	  (progn
	    (end-of-buffer)
	    (newline)
	    (newline)
	    (insert (concat "*************************************************      " (format-time-string "%D %a" (current-time))))))
	(defun timer-mode-start-timing ()
	  (interactive)
	  (progn
	    (setf *OLD* (current-time))
	    (setf *STATUS* t)
	    (timer-mode-do-timing)))

	;clunky insertion of string with tabs in it here. Works OK on my machine(s)
	(defun timer-mode-new-record ()
	  (interactive)
	  (progn
	    (setf refno (read-string "Reference#"))
	    (setf description (read-string "Description:"))
	    (setf est-time (read-string "Estimated time(h)"))
	    (newline)
	    (insert (concat  "00:00:00	0.000	h	" est-time "	(est)	" refno"	" description "	" (format-time-string "%D	%a" (current-time))))
	    (end-of-line)
	    ))
;robs emacs of the ability to do anything other than update the time elapsed
	(defun timer-mode-do-timing()
	  (while (eq 1 1)
	    (progn
	      (setf *now* (current-time)) ;get the time as it is now
	      (setf *secdiff* (- (+ (* (first *now*) (expt 2 16)) (second *now*)) (+ (* (first *OLD*) (expt 2 16)) (second *OLD*)))) ;work out the spacing between now and the last time this function was called. Should be 1 second. SHOULD. 
	      (setf *OLD* *now*)
	      (beginning-of-line)
	      (re-search-forward "[0-9]*:[0-9]*:[0-9]*")
	      (setf whole-match (buffer-substring-no-properties (match-beginning 0) (match-end 0)))
	      (setf seconds (string-to-number(substring whole-match 6 8)))
	      (setf seconds (+ seconds (* 60 (string-to-number(substring whole-match 3 5)))))
	      (setf seconds (+ seconds (* 60 60 (string-to-number(substring whole-match 0 2)))))
	      (setf seconds (+ seconds *secdiff*))
	      (setf *hrs* (/ seconds 3600))
	      (setf *mins* (/ (mod seconds 3600) 60))
	      (setf *secs* (- seconds (+ (* *hrs* 3600) (* *mins* 60))))
	      (beginning-of-line)
	      (re-search-forward "[0-9]+:[0-9]+:[0-9]+")
	      (replace-match (concat (format "%02d" *hrs*) ":" (format "%02d" *mins*) ":" (format "%02d" *secs*)))
	      (re-search-forward "[0-9]*.[0-9]*	h")
	      (replace-match (concat (format "%2.3f" (/ seconds 3600.0)) "	h"))
	      (beginning-of-line)
	      (redisplay t))
	    (sleep-for 1)
	    (redisplay t)))

	(defun timer-mode-new-record-at-bottom ()
	  (interactive)
	  (end-of-line)
	  (timer-mode-new-record))


	(defun timer-get-columns (start-point end-point)
	  (interactive)
					;(message (concat "mark:" (number-to-string start-point) " Point:" (number-to-string end-point)))
	  (goto-char start-point)
	  (setf column-start (current-column))
	  (goto-char end-point)
	  (setf column-end (current-column))
					;(message (concat "start column:" (number-to-string column-start) " End column:" (number-to-string column-end )))
	  (list column-start column-end))

	(defun timer-get-lines (start-point end-point)
	  (interactive)
	  (goto-char start-point)
	  (setf line-start (line-number-at-pos))
	  (goto-char end-point)
	  (setf line-end (line-number-at-pos))
	  (list line-start line-end))
	
;used for doing summing or fraudulent padding
	(defun timer-get-rect-lines (start-point end-point)
	  (interactive)
	  (setf linelist (timer-get-lines start-point end-point))
	  (setf collist (timer-get-columns start-point end-point))
	  (setf rect-lines '())
	  (let ((i (car linelist)))
	    (while (<= i (car (cdr linelist)))
	      (message (number-to-string i))
	      (goto-line i)
	      (beginning-of-line)
					;(forward-char (car collist))
	      (move-to-column (car collist))
	      (setf match-begin (point))
	      (beginning-of-line)
					;(forward-char (car (cdr collist)))
	      (move-to-column (car (cdr collist)))
	      (setf match-end (point))
	      (push (buffer-substring-no-properties match-begin match-end) rect-lines)
	      (incf i)))
	  (message (concat "rect-lines:" (prin1-to-string rect-lines)))
	  rect-lines)

	;when summing, sometimes you'll want to add other hours or something to the total.
	(defun timer-add-hours (start-point end-point)
	  (interactive)
	  (setf extra (string-to-number (read-string "Additional hours? ")))
	  (setf hours (mapcar 'string-to-number (timer-get-rect-lines start-point end-point)))
	  (message (concat "hours:" (prin1-to-string hours)))
	  (eval `(+ ,@hours ,extra)))


	(defun timer-put-rect-lines (start-point end-point times)
	  (interactive)
	  (setf linelist (timer-get-lines start-point end-point))
	  (setf collist (timer-get-columns start-point end-point))
	  (setf rect-lines '())
	  (let ((i (car linelist)))
	    (while (<= i (car (cdr linelist)))
	      (message (number-to-string i))
	      (goto-line i)
	      (beginning-of-line)
					;(forward-char (car collist))
	      (move-to-column (car collist))
	      (setf match-begin (point))
	      (beginning-of-line)
					;(forward-char (car (cdr collist)))
	      (move-to-column (car (cdr collist)))
	      (setf match-end (point))
	      (delete-region match-begin match-end)
					;(push (buffer-substring-no-properties match-begin match-end) rect-lines)
	      (insert (pop times))
	      (incf i)))
	  (message (concat "rect-lines:" (prin1-to-string rect-lines))))
					;rect-lines)

	
	(defun timer-mode-fraudulent-padding (start-point end-point)
	  (interactive)
	  (setf tgthrs (string-to-number (read-string "target hours? ")))

	  (setf hours (mapcar 'string-to-number (timer-get-rect-lines start-point end-point)))
	  (setf totalhours (eval `(+ ,@hours)))

	  (setf diffhrs (- tgthrs totalhours))
	  (setf hourscalc (reverse (mapcar #'(lambda (num) (format "%2.3f" (+ num (* (/ (* num 1.0) totalhours) diffhrs)))) hours)))
					;(message (number-to-string (eval `(+ ,@hourscalc)))))
	  (timer-put-rect-lines start-point end-point hourscalc))

	(defun timer-mode-make-nice ()
	  (interactive)
	  (timer-mode-fraudulent-padding (mark) (point)))
	
	
	(defun timer-mode-sum-hours ()
	  (interactive)
	  (setf tot (timer-add-hours (mark) (point)))
	  (message (concat "hours:" (number-to-string tot))))


	

	)
    ;unsetting stuff - probably not strictly neccessary, but it makes me feel nice.
    (progn
      (message (concat "timer-mode: is nil" ))
      (setq timer-mode-map nil)
      (defun timer-mode-new-day () nil)
      (defun timer-mode-start-timing () nil)
      (defun timer-mode-new-record-at-bottom () nil)
      (defun timer-mode-do-timing () nil)
      (defun timer-mode-new-record () nil)
      )))

 
(define-minor-mode timer-mode
  "Toggle Timer mode"
  nil " timer" timer-mode-map
  (run-hooks 'timer-toggle-hook);you don't seem to need to run the hooks, if the naming is as per conventions, like if you'd called it timer-mode-hook.
)

(provide 'timer-mode)


